home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Language/OS - Multiplatform Resource Library
/
LANGUAGE OS.iso
/
pcl
/
cl-nd-cl.lha
/
clue
/
clio
/
slider.lisp
< prev
next >
Wrap
Text File
|
1990-07-19
|
46KB
|
1,115 lines
;; -*- Mode:Lisp; Package:CLIO-OPEN; Base:10; Lowercase:T; Fonts:(CPTFONT); Syntax:Common-Lisp; -*-
;;;----------------------------------------------------------------------------------+
;;; |
;;; TEXAS INSTRUMENTS INCORPORATED |
;;; P.O. BOX 149149 |
;;; AUSTIN, TEXAS 78714 |
;;; |
;;; Copyright (C) 1989, 1990 Texas Instruments Incorporated. |
;;; |
;;; Permission is granted to any individual or institution to use, copy, modify, and |
;;; distribute this software, provided that this complete copyright and permission |
;;; notice is maintained, intact, in all copies and supporting documentation. |
;;; |
;;; Texas Instruments Incorporated provides this software "as is" without express or |
;;; implied warranty. |
;;; |
;;;----------------------------------------------------------------------------------+
(in-package "CLIO-OPEN")
(export '(
make-slider
slider
)
'clio-open)
(defmacro translate-x-to-y (x x-width slider)
"Translate x coord for horizontal slider into y of a vertical slider.
X-WIDTH is the width in x-direction that must be changed into a y-offset."
`(with-slots (height) ,slider
(- height 1 ,x (max 0 (1- ,x-width)))))
(defmacro confine-to (value minimum maximum)
`(max ,minimum (min ,value ,maximum)))
(defmacro align (value increment)
;; Since we are talking SCALE VALUE here
;; we pixel-round since it may be a REAL number.
`(if (= 1 ,increment)
,value
(* (pixel-round ,value ,increment) ,increment)))
(defmacro value-length (value minimum)
`(- ,value ,minimum))
;;;----------------------------------------------------------------------------+
;;; |
;;; Slider |
;;; |
;;;----------------------------------------------------------------------------+
;; Implementation Strategy:
;;
;; Since CLIO should implement a "look and
;; feel" independent implemementation of open-look, then only those
;; parts of the slider that are going to exist in most implemementations
;; will be supported. Since make-slider accepts :
;; (increment indicator-size maximum minimum orientation update-delay value compress-exposures)
;; then all "features" must be derived from these inputs.
;;
;; To provide numeric visual feedback of the current value is desirable,
;; but providing this as a typein field or read-only field really requires a label
;; or else the displayed result is somewhat confusing.
;; The current value will be implemented as AUTOMATIC tick-marks and tick-mark
;; labels based on the min-max values and the space available to print them. The
;; actual current value will not be printed but will be discernable by "reading the scale".
;;
;; Thus the slider parts implemented are :
;; (bar, drag-box, (automatic) tick-marks, (automatic) tick-text)
;; and the following will NOT be provided :
;; (End boxes, labels, typein fields, non-numeric text of any kind)
;; This means that the read-only min-max current-value fields will be provided only by way
;; of the min-/max tick-mark tick-text labels.
;;
;; When horizontal sliders require max (or min) values of more than 2 digits
;; then the tick-mark & tick-mark-number-labels are difficult to display. In this
;; case a :vertical orientation is more appropriate. If more than 2-digits are used
;; for a :horizontal slider then the tick-mark granularity will be reduced in order
;; to accommodate the width of the digits.
;;
(defcontact slider (core contact)
((increment :type number
:reader scale-increment ;; SETF method defined below
:initarg :increment
:initform 1)
(indicator-size ;; The size of the distance between tick-marks in value units.
;; :off means "no tickmarks or tick labels", 1 will cause tick-mark
;; overlap if there is not enough space to display. [2..N] will
;; cause a tick-spacing of [1..(1- N)].
;;
:type (or number (member :off)) ;; 0 means "automatic" tick mark spacing.
:reader scale-indicator-size ;; SETF method defined below
:initarg :indicator-size
:initform 0)
(maximum :type number
:reader scale-maximum ;; SETF method defined below
:initarg :maximum
:initform 1)
(minimum :type number
:reader scale-minimum ;; SETF method defined below
:initarg :minimum
:initform 0)
(orientation :type (member :horizontal :vertical)
:reader scale-orientation ;; SETF method defined below
:initarg :orientation
:initform :horizontal)
(update-delay :type (or number (member :until-done))
:reader scale-update-delay ;; SETF method defined below
:initarg :update-delay
:initform 0)
(value :type number
:reader scale-value ;; SETF method defined below
:initarg :value
:initform 0)
(compress-exposures
:initform :on
:type (member :off :on)
:reader contact-compress-exposures
:allocation :class)
;; Internal storage slots
(font :type fontable);; font for current scale
(min-text-width :type number) ;; pixel width of minimum value print string
(max-text-width :type number) ;; pixel width of maximum value print string
(dimensions :type list) ;; (getf *slider-dimensions* scale)
(middle-length :type number) ;; pixel length between first & last tick marks
)
(:resources
increment indicator-size maximum minimum orientation update-delay value
(border-width :initform 0)
(event-mask :initform #.(make-event-mask :exposure :pointer-motion-hint))))
;;;----------------------------------------------------------------------------+
;;; |
;;; Setf Accessors |
;;; |
;;;----------------------------------------------------------------------------+
(defmethod (setf scale-orientation) (new-orientation (slider slider))
(with-slots (orientation width height) slider
(unless (eq orientation new-orientation)
(check-type new-orientation (member :horizontal :vertical))
(setf orientation new-orientation)
(multiple-value-bind (new-width new-height)
(preferred-size slider :width height :height width)
(change-geometry slider :width new-width :height new-height :accept-p t))))
new-orientation)
(defmethod (setf scale-update-delay) (new-update-delay (slider slider))
(with-slots (update-delay) slider
(assert (or (eq new-update-delay :until-done)
(and (numberp new-update-delay) (not (minusp new-update-delay)))) (new-update-delay)
"~a is neither :UNTIL-DONE or a non-negative number." new-update-delay)
(setf update-delay new-update-delay)))
(defmethod (setf scale-value) (new-value (slider slider))
(scale-update slider :value new-value)
new-value)
(defmethod (setf scale-minimum) (new-minimum (slider slider))
(scale-update slider :minimum new-minimum)
new-minimum)
(defmethod (setf scale-maximum) (new-maximum (slider slider))
(scale-update slider :maximum new-maximum)
new-maximum)
(defmethod (setf scale-increment) (new-increment (slider slider))
(scale-update slider :increment new-increment)
new-increment)
(defmethod (setf scale-indicator-size) (new-indicator-size (slider slider))
(scale-update slider :indicator-size new-indicator-size)
new-indicator-size)
;;;------------------------------------------------------------------------------------+
;;; |
;;; Helper Functions |
;;; |
;;;------------------------------------------------------------------------------------+
(defun slider-tick-mark-thickness (slider)
(if (eq :extra-large (contact-scale slider))
3
2))
(defun slider-bar-tick-gap (slider)
;; Distance top of tick-mark and nearest point on bar
(1+ (case (contact-scale slider)
(:small 1) (:medium 2) (:large 3) (:extra-large 4))))
(defun slider-margin (slider margin)
"Returns the MARGIN of SLIDER, one of :min :top :text :max"
;; This is initially *slider-default-margin* until
;; after the PREFERRED-SIZE method is called. Then margins include
;; any additional increase due to a width or height larger than the
;; preferred size. :LEFT means the left margin for this particular orientation.
(assert (member margin '(:min :top :text :max)) (margin)
"~a is an illegal margin" margin)
(let ((margins (getf (getf (window-plist slider) :slider-info) :margins)))
(or (getf margins margin)
;; Calling before margins are setup is never
;; done but code is here for completeness
*slider-default-margin*)))
(defun first-tick-offset (slider)
;; Offset, not including (slider-margin slider :min), from
;; min edge of contact to CENTERLINE of first tick-mark.
(with-slots (min-text-width orientation font dimensions indicator-size) slider
(let ((tick-mark-offset (slidebar-tick-mark-offset dimensions))
(gap (slidebar-gap dimensions)))
(+ *slider-default-margin*
;; Add GAP below since drag-box clear-gap-around extends past bar edge
(if (eq :off indicator-size)
(+ gap tick-mark-offset)
(if (eq orientation :horizontal)
(max (floor min-text-width 2) (+ gap tick-mark-offset))
(+ gap tick-mark-offset) ;; the text baseline is ALWAYS above end of bar MIN
))))))
(defun last-tick-offset (slider)
;; Offset, not including (slider-margin slider :max), from
;; max edge of contact to CENTERLINE of last tick-mark.
(with-slots (max-text-width orientation font dimensions indicator-size) slider
(let ((tick-mark-offset (slidebar-tick-mark-offset dimensions))
(gap (slidebar-gap dimensions)))
(+ *slider-default-margin*
;; Add GAP below since drag-box clear-gap-around extends past bar edge
(if (eq :off indicator-size)
(+ gap tick-mark-offset)
(if (eq orientation :horizontal)
(max (ceiling max-text-width 2) (+ gap tick-mark-offset))
(+ tick-mark-offset
(max gap
;; font-ascent may go beyond end of bar MAX if font is bigger than "point" requested
(abs (- (cadr (getf (slidebar-bar-text-offset dimensions) orientation))
(max-char-ascent font)
))))))))))
;;
;;
;; Pixels : Scale Units :
;;
;; middle-length (- maximum minimum)
;;
;; [MAX]
;; |
;; |
;; | proportional [MAX]
;; | - |
;; | |
;; | Pixel-delta | -
;; | | Scale-delta
;; | |
;; [MIN] - [MIN] -
;;
;; 0 minimum
;;
;; Since :
;;
;; pixel-delta / middle-length = scale-delta / (- maximum minimum)
;;
;; Then :
;;
;; Pixel-delta = (* scale-delta middle-length) / (- maximum minimum)
;;
;; And :
;;
;; Scale-delta = (* pixel-delta (- maximum minimum)) / middle-length
;;
(defun units-to-pixels (slider scale-delta)
;; Convert a scale delta to a pixel delta
(with-slots (minimum maximum middle-length) slider
(pixel-round (/ (* scale-delta middle-length)
(- maximum minimum)))))
(defun pixels-to-units (slider pixel-delta)
(with-slots (orientation minimum maximum increment middle-length) slider
;; Convert a pixel delta to a scale units delta
;; *DON'T* round this - units may be fractional !
(/ (* pixel-delta (- maximum minimum))
middle-length)))
;; NOTE: The functions named ????-x and below return values strictly for a :horizontal
;; slider and the return values must be translated for a :vertical slider.
(defun first-tick-x (slider)
(+ (slider-margin slider :min)
(first-tick-offset slider)))
(defun drag-box-center-x (slider &optional (scale-value (scale-value slider)))
(with-slots (minimum) slider
;; Returns dead center of drag-box
(+ (first-tick-x slider)
;; Must subtract minimum since minimum can be negative and is NOT always zero!
(units-to-pixels slider (value-length scale-value minimum)))))
(defun drag-box-min-x (slider &optional (scale-value (scale-value slider)))
(with-slots (dimensions minimum maximum) slider
(let* ((drag-box-width (slidebar-drag-box-width dimensions))
(gap (slidebar-gap dimensions)))
(- (drag-box-center-x slider scale-value)
(floor drag-box-width 2)
gap ;; subtract whitespace gap around drag-box
))))
(defun drag-box-position (slider &optional (scale-value (scale-value slider)))
(declare (values x y width height))
(with-slots (orientation ) slider
;; Return values describing area of drag-box for SCALE-VALUE
(let* ((drag-min-edge (drag-box-min-x slider scale-value))
(drag-image (getf (getf *slider-drag-box-images* orientation)
(contact-scale slider))))
(if (eq orientation :horizontal)
(values drag-min-edge
(+ (slider-margin slider :top) *slider-default-margin*)
(image-width drag-image)
(image-height drag-image))
(values (+ (slider-margin slider :top) *slider-default-margin*)
(translate-x-to-y drag-min-edge (image-height drag-image) slider)
(image-width drag-image)
(image-height drag-image)))
)))
(defmethod scale-update ((slider slider) &key value minimum maximum indicator-size increment)
;; Called by (method initialize-instance :after (slider)) to do error checking, and by
;; SETF methods for slots in arglist above, and by (setf scale-value) called to move slider.
(with-slots
((current-val value)
(current-min minimum)
(current-max maximum)
(current-ind indicator-size)
(current-inc increment)
orientation min-text-width max-text-width font)
slider
(let ((old-val (and value current-val)) ;; old-value & flag that value was passed in.
(old-min (and minimum current-min))
(old-max (and maximum current-max))
(old-inc (and increment current-inc))
(old-ind (and indicator-size current-ind)))
(setf minimum (or minimum current-min)
maximum (or maximum current-max)
value (or value (confine-to current-val minimum maximum))
indicator-size (or indicator-size current-ind)
increment (or increment current-inc))
(assert (and (numberp minimum) (numberp maximum)
(< minimum maximum))
(minimum maximum)
"Minimum (~a) is not less than maximum (~a)."
minimum maximum)
(assert (and (numberp value)
(<= minimum value maximum))
(value)
"Value (~a) must be in the range [~a, ~a]."
value minimum maximum)
(assert (or (eq :off indicator-size)
(and (numberp indicator-size)
(not (minusp indicator-size))))
(indicator-size)
"Indicator-size (~a) must be :OFF, 0, or a positive number."
indicator-size)
(assert (and (numberp increment)
(< 0 increment (1+ (- maximum minimum))) ;; allow fractional increments, allow increment = maximum
(zerop (mod (- maximum minimum) increment)))
(increment)
"Increment (~a) must be in the range [0 ~a] and a factor of ~:*~d."
increment (- maximum minimum))
;; Once VALUE & INCREMENT are valid we can align VALUE, if necessary,
;; to be a multiple of INCREMENT.
(setq value (+ minimum (align (value-length value minimum) increment)))
(setf current-min minimum
current-max maximum
current-val value
current-ind indicator-size
current-inc increment
min-text-width (text-extents font (format nil "~a" minimum))
max-text-width (text-extents font (format nil "~a" maximum)))
;; Redisplay drag-box and any changes
(when (realized-p slider)
(cond ((or (and old-min
(not (= old-min current-min)))
(and old-max
(not (= old-max current-max)))
(and old-ind
(not (eq old-ind current-ind)))
(and old-inc
(not (= old-inc current-inc))))
(clear-area slider :exposures-p t))
((and old-val ;; when called with NEW increment value
(not (= old-val current-val))) ;; when something has changed
;; Compute area of old drag-box ( if any )
(multiple-value-bind (old-x old-y old-width old-height)
(drag-box-position slider old-val)
;; Compute area of new drag-box
(multiple-value-bind (x y width height)
(drag-box-position slider current-val)
;; Merge areas to redisplay : new drag-box, bar between old & new,
;; old drag-box ( if any ), & tick marks obscured by drag-box
(when old-val
(if (eq orientation :horizontal)
(setf width (+ (abs (- x old-x)) (max old-width width))
x (min x old-x))
(setf height (+ (abs (- y old-y)) (max old-height height))
y (min y old-y))))
(clear-area slider :x x :y y :width width :height height)
(display slider x y width height))))
(t)))
)))
;;;----------------------------------------------------------------------------+
;;; |
;;; Initialization |
;;; |
;;;----------------------------------------------------------------------------+
(defun make-slider (&rest initargs &key &allow-other-keys)
(apply #'make-contact 'slider initargs))
(defun bar-bottom-offset (slider)
;; Offset from the top (left) of horizontal (vertical) slider
;; ... does NOT include (slider-margin slider :top) ...
;; and 1-pixel past the bottom (right) edge of the slidebar
(with-slots (dimensions) slider
(+ *slider-default-margin*
(slidebar-gap dimensions)
(slidebar-bar-drag-offset dimensions)
(slidebar-bar-thickness dimensions))))
(defun fixed-thickness (slider &key include-text-p)
;; The minimum thickness of slider required for the
;; scale, orientation, and string characteristics of the minimum & maximum
(with-slots (orientation min-text-width max-text-width
indicator-size dimensions font) slider
(let ((x (first (getf (slidebar-bar-text-offset dimensions) orientation)))
(y (second (getf (slidebar-bar-text-offset dimensions) orientation)))
(scale (contact-scale slider)))
(if (eq :off indicator-size)
(+ *slider-default-margin*
(if (eq orientation :horizontal)
(image-height (getf (getf *slider-drag-box-images* orientation) scale))
(image-width (getf (getf *slider-drag-box-images* orientation) scale)))
*slider-default-margin*) ;; no space allocated for tick marks & text
;; else
(if (eq orientation :horizontal)
(+ (bar-bottom-offset slider) y
(if include-text-p
(+ (max-char-descent font) *slider-default-margin*)
0))
(+ (bar-bottom-offset slider) x
(if include-text-p
(+ (max min-text-width max-text-width) *slider-default-margin*)
0)))))))
(defun slider-compute-margins (slider)
;; Now margins can be computed from the delta between the size needed and the
;; size we were given. The length of the slider basically stretches to fit but
;; any extra height results in the slider being centered in space provided.
;; PREFERRED-SIZE (via initialize-instance :after) MUST have been called to
;; set WIDTH & HEIGHT by this time.
(with-slots (orientation width height middle-length) slider
(let* ((total-min-thickness (fixed-thickness slider :include-text-p t))
(size (if (eq orientation :horizontal) height width))
(top-margin (floor (- size total-min-thickness) 2))
(bottom-margin (- size total-min-thickness top-margin)))
(setf (getf (getf (window-plist slider) :slider-info) :margins)
;; left top bottom right (horizontal)
(list :min 0 :top top-margin :text bottom-margin :max 0))
;; With margins set we can now compute and save middle-length for efficiency
(setf middle-length
(- (if (eq :horizontal orientation)
width
height)
(slider-margin slider :min)
(first-tick-offset slider)
(last-tick-offset slider)
(slider-margin slider :max)
)))))
(defmethod initialize-instance :after ((slider slider) &key &allow-other-keys)
(with-slots (font width height minimum maximum
dimensions min-text-width max-text-width) slider
(setq font (find-font slider *default-display-text-font*)
dimensions (getf *slider-dimensions* (contact-scale slider)))
(scale-update slider) ;; do some error checking, set min-text-width, etc.
;; Initialize required geometry
(multiple-value-setq (width height) (preferred-size slider))
;; Compute margins now that WIDTH & HEIGHT are known
(slider-compute-margins slider)
))
;;;----------------------------------------------------------------------------+
;;; |
;;; Geometry Management |
;;; |
;;;----------------------------------------------------------------------------+
(DEFMETHOD rescale :before ((slider slider))
(with-slots (font dimensions) slider
(setf font (find-font slider *default-display-text-font*)
dimensions (getf *slider-dimensions* (contact-scale slider)))
(slider-compute-margins slider)
))
(defmethod resize :after ((slider slider) new-width new-height new-border-width)
;; This method duplicates calculations started in (method initialize-instance :after (slider))
;; but are done here since they also must be performed when change-geometry is invoked.
;; Called when window-manager or someone else calls change-geometry.
(declare (ignore new-width new-height new-border-width))
(slider-compute-margins slider))
(defmethod preferred-size ((slider slider) &key width height border-width)
(declare (ignore border-width)) ;; preferred-border-width is 0
(with-slots (orientation min-text-width max-text-width font dimensions indicator-size
(current-height height) (current-width width)) slider
(let* ((drag-box-width (slidebar-drag-box-width dimensions))
(tick-mark-offset (slidebar-tick-mark-offset dimensions))
;; Min width of slider with 2 positions = double size of drag box
(minimum-double-width
(+ (- (first-tick-offset slider) tick-mark-offset)
(max (* 2 drag-box-width)
(if (eq :off indicator-size)
0
(if (eq orientation :horizontal)
;; room needed to display text between first/last-tick
(+ (ceiling min-text-width 2)
(max-char-descent font)
(floor max-text-width 2))
;; room needed to display 2 text lines (min & max) vertically,
;; plus a small gap between
(+ (max-char-ascent font)
(max-char-descent font) ;; gap between
(max-char-ascent font)))))
(- (last-tick-offset slider) tick-mark-offset)))
;; Calculate geometry assuming :horizontal orientation
(preferred-height
(max
;; Suggested or current height
(if (eq orientation :horizontal)
(or height current-height)
(or width current-width))
;; Total thickness of horizontal bar
(fixed-thickness slider :include-text-p t)))
(preferred-width
(max
;; Suggested or current width
(if (eq orientation :horizontal)
(or width current-width)
(or height current-height))
minimum-double-width))
)
;; Return preferred geometry according to actual orientation
(if (eq orientation :horizontal) ;; preferred-border-width is always 0
(values preferred-width preferred-height 0)
(values preferred-height preferred-width 0))
)))
;;;----------------------------------------------------------------------------+
;;; |
;;; Event Translations |
;;; |
;;;----------------------------------------------------------------------------+
(defevent slider (:button-press :button-1) slider-press)
(defevent slider (:button-release :button-1) slider-release)
(defevent slider (:motion-notify :button-1) slider-handle-motion)
(defun slider-release (slider)
(declare (special *slider-pressed-p*))
(when (boundp '*slider-pressed-p*)
(throw-action slider :release t)))
(defun highlite-drag-box (slider gc x y width height gap)
(draw-rectangle slider gc (+ gap 1 gap x) (+ gap 1 gap y)
(- width (* 4 gap) 3) (- height (* 4 gap) 3) :fill-p))
(defun slider-press (slider)
(with-event (x y)
(with-slots
(foreground orientation update-delay minimum maximum
increment width height display value dimensions)
slider
(let (*slider-pressed-p* )
(declare (special *slider-pressed-p*))
(multiple-value-bind (drag-x drag-y drag-width drag-height)
(drag-box-position slider)
(when
(cond
((and (>= x drag-x) (< x (+ drag-x drag-width))
(>= y drag-y) (< y (+ drag-y drag-height)))
;; SELECT on drag box
(let ((*highlight-pixel* (logxor foreground (contact-current-background-pixel slider)))
(gap (slidebar-gap dimensions)))
(declare (special *highlight-pixel*)) ;; use this in display method while moving ..
(using-gcontext
(gc :drawable slider
:function boole-xor
:foreground *highlight-pixel*)
;; Highlight drag area
(highlite-drag-box slider gc drag-x drag-y drag-width drag-height gap)
;; Set timer for update
(when (and (numberp update-delay) (plusp update-delay))
(add-timer slider :update-delay update-delay))
(apply-callback slider :begin-continuous)
(catch :release
(let ((*previous-position* (if (eq :vertical orientation) y x)))
(declare (special *previous-position*))
(loop (process-next-event display))))
(apply-callback slider :end-continuous)
;; Unhighlight drag area.
(multiple-value-bind (new-drag-x new-drag-y)
(drag-box-position slider)
(highlite-drag-box slider gc new-drag-x new-drag-y drag-width drag-height gap))))
t)
;; SELECT on bar
;; Since it is NOT in drag-box, just check if it is in bar
;; or the area of the bar if it had the thickness of the drag-box.
;; This makes clicking somewhat easier.
((multiple-value-bind (bar-x bar-y bar-width bar-height)
(if (eq orientation :horizontal)
(values (slider-margin slider :min) (slider-margin slider :top)
(- width (slider-margin slider :min) (slider-margin slider :max))
drag-height)
(values (slider-margin slider :top) (slider-margin slider :min)
drag-width
(- height (slider-margin slider :min) (slider-margin slider :max))))
(and (>= x bar-x) (>= y bar-y)
(< x (+ bar-x bar-width))
(< y (+ bar-y bar-height))))
;; Advance drag-box one increment in direction indicated.
;; User may click so fast that the drag box passes the click
;; position, thus inadvertently reversing the increment direction.
;; Synchronize by using current pointer position, not click position.
(multiple-value-bind (ptr-x ptr-y) (pointer-position slider)
(let ((delta (if (if (eq orientation :horizontal)
(< ptr-x drag-x)
(>= ptr-y (+ drag-y drag-height)))
(- increment)
increment))
(gap (slidebar-gap dimensions)))
(slider-increment-value slider delta)
;; Must warp pointer to stay in MIN (or MAX) bar, if necessary
(multiple-value-bind (new-drag-x new-drag-y drag-width drag-height)
(drag-box-position slider)
(multiple-value-bind (warp-x warp-y)
(if (eq orientation :horizontal)
(if (plusp delta)
(let ((min-x (min (1- width) (+ new-drag-x drag-width gap))))
(when (< ptr-x min-x)
(values min-x ptr-y)))
(let ((max-x (max 0 (- new-drag-x gap))))
(when (< max-x ptr-x)
(values max-x ptr-y))))
(if (minusp delta)
(let ((min-y (min (1- height) (+ new-drag-y drag-height gap))))
(when (< ptr-y min-y)
(values ptr-x min-y)))
(let ((max-y (max 0 (- new-drag-y gap))))
(when (< max-y ptr-y)
(values ptr-x max-y)))))
(when warp-x
(warp-pointer slider warp-x warp-y))))))
t))
;; Report final value, if necessary
(unless (eql 0 update-delay)
(delete-timer slider :update-delay)
(apply-callback slider :new-value value))))))))
(defun slider-increment-value (slider scale-increment)
"Convert the scale-increment to a (possibly) new scale position
and (possibly) cause the slider to be updated."
(with-slots (value orientation increment minimum maximum update-delay) slider
;; Must use truncate for negative scale-increment's - rounds to zero.
(let* ((new-value (+ value scale-increment))
(adjusted (confine-to (or (apply-callback slider :adjust-value new-value)
new-value)
minimum maximum)))
(unless (= value adjusted) ;; unless no change in slider scale occurs
(setf (scale-value slider) adjusted) ;; <- this calls scale-update & redisplays slider
(when (eql 0 update-delay)
(apply-callback slider :new-value adjusted))))))
(defun slider-handle-motion (slider)
(declare (special *previous-position*))
(when (boundp '*previous-position*)
(with-slots (orientation increment) slider
(with-event (state x y)
(multiple-value-bind (ptr-x ptr-y)
;; Is :button-1 still down?
(if (plusp (logand state #.(make-state-mask :button-1)))
;; Yes, query current pointer position
(pointer-position slider)
;; No, use final x,y returned for button transition
(values x y))
(let
((modulo-increment
(* (truncate
(pixels-to-units
slider
(if (eq :horizontal orientation)
(- ptr-x *previous-position*)
;; Must swap order of subtraction since positive y direction
;; is negative scale direction for :vertical slider
(- *previous-position* ptr-y)))
increment)
increment)))
;; Convert the pixel motion to a suitable slider scale motion
(unless (zerop modulo-increment)
(slider-increment-value slider modulo-increment)
;; Use drag-box position. Ptr position is only correct if the drag-box can
;; move to the ptr posiiton without bumping up against the min/max limits.
(setf *previous-position*
(if (eq orientation :horizontal)
(drag-box-center-x slider)
(translate-x-to-y (drag-box-center-x slider) 1 slider))
))))))))
(defun choose-indicator-size (slider)
"Returns TICK-LIMIT = the number of ticks to draw."
;; Called when indicator-size eq :off to automatic tick-marks
(declare (values tick-limit increments-in-tick))
(with-slots (maximum minimum increment) slider
(let* ((tick-mark-thickness (slider-tick-mark-thickness slider))
(increments-in-tick 1)
(min-visible-width (* 2 tick-mark-thickness))
(ticks (floor (- maximum minimum) increment))
)
;; Return appropriate tick-limit
(values
(1+ ;; 1+ since we draw the first-tick plus any calculated ticks
(do* ((ticks-visible nil))
((cond
((<= ticks 1) (setq ticks-visible 1)) ;; reached minimum ticks, 1 at each end
((>= (units-to-pixels slider (* increments-in-tick increment))
min-visible-width)
(setq ticks-visible ticks)))
;; Exit form
(return ticks-visible))
(setq ticks (floor ticks 2)
increments-in-tick (* 2 increments-in-tick))))
increments-in-tick ;; 2nd return value
))))
;;;----------------------------------------------------------------------------+
;;; |
;;; Display |
;;; |
;;;----------------------------------------------------------------------------+
;;; d1 = first-tick-x
;;; d2 = slidebar-tick-mark-offset
;;;
;;; min fill drag empty max
;;; +-----------------------------------+ -
;;; | | gap
;;; | +----+ | -
;;; | | || | drag-bar-offset
;;; | .-- ----- | || -------- --. | -
;;; | |** ***** | || | | bar-thickness
;;; | `-- ----- | || -------- --' | -
;;; | |---+| |
;;; |<-----> +----+ |
;;; | d1 || || || | bar-text-offset
;;; | <--> |
;;; | d2 |
;;; | MIN MAX |
;;; +-----------------------------------+ -
;;; ^^ ^ ^ ^ ^^ ^
;;; || | | | || +-- bar-max-x
;;; || | | | |+---- (<= last-tick-x MAX-UPPER-EDGE bar-max-x)
;;; || | | | +----- last-tick-x
;;; || | | +------------- drag-max-edge
;;; || | +--------------------- drag-min-edge
;;; || +--------------------------- first-tick-x
;;; |+----------------------------- (<= bar-min-x MIN-LOWER-EDGE first-tick-x )
;;; +------------------------------ bar-min-x
;;;
;;; Note: No margins are shown except *slider-default-margin*
;;;
;;;
;;; .--.
;;; | | max
;;;
;;; | |
;;; | |
;;; | | empty
;;; | |
;;;
;;; +----+
;;; | |
;;; | | drag
;;; | |
;;; +----+
;;;
;;; |**|
;;; |**| fill
;;; |**|
;;; |**|
;;;
;;; |**| min
;;; `--'
;;;
(defmethod display ((slider slider) &optional at-x at-y at-width at-height &key)
(with-slots (dimensions width height foreground orientation
minimum maximum increment middle-length sensitive
min-text-width max-text-width indicator-size font) slider
;; Default exposed rectangle, if necessary
(setf at-x (or at-x 0)
at-y (or at-y 0)
at-width (or at-width (- width at-x))
at-height (or at-height (- height at-y)))
(let* ((drag-box-width (slidebar-drag-box-width dimensions))
(drag-bar-offset (slidebar-bar-drag-offset dimensions))
(gap (slidebar-gap dimensions))
(bar-thickness (slidebar-bar-thickness dimensions))
(bar-image (getf (getf *slider-bar-images* :masks) (contact-scale slider)))
(image-half-size (floor (image-width bar-image) 2)) ;; image is BOTH ends, use min half
(first-tick-x (first-tick-x slider))
(last-tick-x (+ first-tick-x middle-length))
(bar-y (+ (slider-margin slider :top) *slider-default-margin*
gap drag-bar-offset))
(bar-min-x (- first-tick-x ;; ZRP
(slidebar-tick-mark-offset dimensions)))
(bar-max-x (+ last-tick-x (slidebar-tick-mark-offset dimensions)))
(drag-min-edge (drag-box-min-x slider))
(drag-max-edge (+ drag-min-edge gap drag-box-width gap))
(end-portion (min image-half-size (max 0 (- drag-min-edge bar-min-x)))) ;; for min end ONLY
(min-lower-edge (+ bar-min-x end-portion))
(max-upper-edge (max (- bar-max-x image-half-size) drag-max-edge))
(mask (contact-image-mask slider bar-image
:foreground foreground
:background (contact-current-background-pixel slider)))
(inactive-p (not (sensitive-p slider)))
(scale (contact-scale slider))
)
;; First draw the bar outline, then
;; draw the tick-marks, draw the tick-text, and
;; finally, fill the bar then blt the drag-box to the correct position.
(using-gcontext (gc :drawable slider
:font font
:exposures :off
:foreground (if inactive-p
(logxor foreground (contact-current-background-pixel slider))
foreground)
:fill-style (when inactive-p :stippled)
:function (when inactive-p boole-xor)
;; Use 50%gray, since 25%gray looks bad (bar disappears) for args :
;; (make-slider :width 200 :height 200 :maximum 4 :orientation :vertical :scale :medium)
:stipple (when inactive-p (contact-image-mask slider 50%gray :depth 1))
:clip-mask (list at-x at-y at-width at-height)
)
;; Draw MIN end - if it will be visible after drag-box is drawn later.
;; The zero reference point (ZRP) is the center of the first tick-mark.
;; If at this position we just draw the drag-box at ZRP after subtracting
;; the half-width of the drag-box to get the coordinate of the left edge.
;; Actually the image blt to the slider also contains a gap, but the ZRP is
;; situated such that it centers the drag-box at the extreme min position.
;; Draw (at least part of) MIN
(when (> drag-min-edge bar-min-x) ;; drag-box is NOT less than gap away from MIN edge
(multiple-value-bind (src-x src-y -width -height dst-x dst-y)
(if (eq orientation :horizontal)
(values 0 0 end-portion (image-width bar-image) bar-min-x bar-y)
(values 0 (- (image-width bar-image) image-half-size) ;; image may have odd # of pixels
(image-width bar-image) end-portion
bar-y (translate-x-to-y bar-min-x end-portion slider)))
(when (area-overlaps-p at-x at-y at-width at-height dst-x dst-y -width -height)
(if inactive-p
(draw-rectangle slider gc dst-x dst-y -width -height :fill-p)
(copy-area mask gc src-x src-y -width -height slider dst-x dst-y))))
;; Draw FILL, if any
(when (> drag-min-edge min-lower-edge)
(multiple-value-bind (x y -width -height)
(if (eq orientation :horizontal)
(values min-lower-edge bar-y (- drag-min-edge min-lower-edge) bar-thickness)
(values bar-y (translate-x-to-y min-lower-edge (- drag-min-edge min-lower-edge) slider)
bar-thickness (- drag-min-edge min-lower-edge)))
(when (area-overlaps-p at-x at-y at-width at-height x y -width -height)
(draw-rectangle slider gc x y -width -height :fill-p))))
)
;; Draw EMPTY portion, if any
(when (> max-upper-edge drag-max-edge)
(multiple-value-bind (x y x2 y2 x3 y3 x4 y4)
(if (eq orientation :horizontal)
(values drag-max-edge bar-y ;; x y
max-upper-edge bar-y ;; x2 y2
drag-max-edge (+ bar-y bar-thickness -1) ;; x3 y3
max-upper-edge (+ bar-y bar-thickness -1)) ;; x4 y4
(values bar-y (translate-x-to-y drag-max-edge 1 slider) ;; x y
bar-y (translate-x-to-y max-upper-edge 1 slider) ;; x2 y2
(+ bar-y bar-thickness -1) (translate-x-to-y drag-max-edge 1 slider) ;; x3 y3
(+ bar-y bar-thickness -1) (translate-x-to-y max-upper-edge 1 slider))) ;; x4 y4
(when (area-overlaps-p at-x at-y at-width at-height x y (- x2 x) bar-thickness)
(draw-segments slider gc (list x y x2 y2 x3 y3 x4 y4))))
)
;; Draw MAX, or portion not obscured by drag-box
(when (plusp (setq end-portion (min image-half-size (- bar-max-x max-upper-edge))))
(setq mask (contact-image-mask slider (getf (GETF *slider-bar-images* :borders) scale)
:foreground foreground
:background (contact-current-background-pixel slider)))
(multiple-value-bind (src-x src-y -width -height dst-x dst-y)
(if (eq orientation :horizontal)
(values (- (image-width bar-image) end-portion) 0
end-portion (image-width bar-image)
max-upper-edge bar-y)
(values 0 0
(image-width bar-image) end-portion
bar-y (translate-x-to-y max-upper-edge end-portion slider)))
(when (area-overlaps-p at-x at-y at-width at-height dst-x dst-y -width -height)
(if inactive-p
(draw-rectangle slider gc dst-x dst-y -width -height :fill-p)
(copy-area mask gc src-x src-y -width -height slider dst-x dst-y))))
)
;; Draw TICK-TEXT, the labels for MIN and MAX
(unless (eq :off indicator-size) ;; don't draw tick marks or tick text
(let ((min-thickness (+ (slider-margin slider :top)
(fixed-thickness slider :include-text-p nil)))
(text-x-offset (first (getf (slidebar-bar-text-offset dimensions) orientation)))
(text-y-offset (second (getf (slidebar-bar-text-offset dimensions) orientation))))
(multiple-value-bind (x-min y-min x-max y-max)
(if (eq orientation :horizontal)
(values (+ (slider-margin slider :min)
text-x-offset
;; center text at :first-tick
(+ (first-tick-offset slider)
(- (floor min-text-width 2))))
min-thickness
(- width
(slider-margin slider :max)
(last-tick-offset slider)
(ceiling max-text-width 2)) ;; scoot left to fit on odd widths!
min-thickness)
(values min-thickness
(+ (slider-margin slider :max)
(last-tick-offset slider)
middle-length
(slidebar-tick-mark-offset dimensions)
(- gap))
min-thickness
(+ (slider-margin slider :max)
(last-tick-offset slider)
(- (slidebar-tick-mark-offset dimensions))
text-y-offset)))
(let* ((font-ascent (max-char-ascent font))
(font-height (+ font-ascent (max-char-descent font))))
(when (if (eq orientation :horizontal)
(area-overlaps-p at-x at-y at-width at-height
x-min (- y-min font-ascent)
(+ (- x-max x-min) max-text-width)
font-height)
(area-overlaps-p at-x at-y at-width at-height
x-max (- y-max font-ascent)
(max min-text-width max-text-width)
(+ (- y-min y-max) font-height)))
;; Draw TICK-TEXT for min and max
(draw-glyphs slider gc x-min y-min (format nil "~a" minimum))
(draw-glyphs slider gc x-max y-max (format nil "~a" maximum))))))
;; Draw TICK-MARKS
(multiple-value-bind (tick-limit increments-in-tick)
(if (plusp indicator-size)
(values (1+ (floor (- maximum minimum) (* increment indicator-size)))
indicator-size)
(choose-indicator-size slider)) ;; automatic tick marks
(do* ((tick 0 (incf tick))
(tick-thickness (slider-tick-mark-thickness slider))
(tick-x (+ (first-tick-x slider) (- (floor tick-thickness 2))) ;; adjust from center to edge of tick
(+ (first-tick-x slider) (- (floor tick-thickness 2))
(units-to-pixels slider (* tick increments-in-tick increment))))
(tick-y (+ (slider-margin slider :top)
(bar-bottom-offset slider)
(slider-bar-tick-gap slider)))
(tick-height (slidebar-tick-mark-length dimensions)))
((= tick tick-limit)) ;; draw tick @min plus TICK-LIMIT more
(multiple-value-bind (x y -width -height)
(if (eq orientation :horizontal)
(values tick-x tick-y
tick-thickness tick-height)
(values tick-y (translate-x-to-y tick-x tick-thickness slider)
tick-height tick-thickness))
(when (area-overlaps-p at-x at-y at-width at-height x y -width -height)
(draw-rectangle slider gc x y -width -height :fill-p)
))))
)
;; Draw DRAG BOX (possibly over a tick mark)
(let ((drag-image (getf (getf *slider-drag-box-images* orientation) scale)))
(setq mask (contact-image-mask slider drag-image
:foreground foreground
:background (contact-current-background-pixel slider)))
(multiple-value-bind (src-x src-y -width -height dst-x dst-y)
(if (eq orientation :horizontal)
(values 0 0 (image-width drag-image) (image-height drag-image)
drag-min-edge (- bar-y drag-bar-offset gap))
(values 0 0 (image-width drag-image) (image-height drag-image)
(- bar-y drag-bar-offset gap)
(translate-x-to-y drag-min-edge (image-height drag-image) slider)))
(when (area-overlaps-p at-x at-y at-width at-height
dst-x dst-y -width -height)
(if inactive-p
(draw-rectangle slider gc dst-x dst-y -width -height :fill-p)
(copy-area mask gc src-x src-y -width -height slider dst-x dst-y))
(when (boundp '*highlight-pixel*)
(special-highlite-drag-box slider gc dst-x dst-y -width -height gap)))))))))
;;; Crock! This function could be inlined, except that causes the Explorer compiler
;;; to barf on (method display (slider)) when using R4 CLX.
(defun special-highlite-drag-box (slider gc dst-x dst-y -width -height gap)
(declare (special *highlight-pixel*))
(with-gcontext (gc :function boole-xor :foreground *highlight-pixel*)
;; Highlight drag area while button is still down )
(highlite-drag-box slider gc dst-x dst-y -width -height gap)))